package com.kalvan.swagger.dubbo.web;

import com.kalvan.swagger.dubbo.config.ReferenceManager;
import com.kalvan.swagger.dubbo.reader.NameDiscover;
import io.swagger.annotations.Api;
import io.swagger.util.Json;
import io.swagger.util.PrimitiveType;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * @author chenliang
 */
@Controller
@RequestMapping("${swagger.dubbo.http:h}")
@Api(hidden = true)
public class DubboHttpController {

    private static Logger logger = LoggerFactory.getLogger(DubboHttpController.class);

    private static final String CLUSTER_RPC = "rpc";

    @Value("${swagger.dubbo.enable:true}")
    private boolean enable = true;

    @Value("${swagger.dubbo.cluster:rpc}")
    private String cluster = CLUSTER_RPC;

    @RequestMapping(value = "/{interfaceClass}/{methodName}")
    @ResponseBody
    public ResponseEntity invokeDubbo(@PathVariable("interfaceClass") String interfaceClass,
                                      @PathVariable("methodName") String methodName, HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {
        return invokeDubbo(interfaceClass, methodName, null, request, response);
    }

    @RequestMapping(value = "/{interfaceClass}/{methodName}/{operationId}")
    @ResponseBody
    public ResponseEntity invokeDubbo(@PathVariable("interfaceClass") String interfaceClass,
                                      @PathVariable("methodName") String methodName,
                                      @PathVariable("operationId") String operationId, HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {
        if (!enable) {
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        }
        //根据参数获取具体方法
        Method method = ReferenceManager.matchRefMethod(interfaceClass, methodName, operationId, request.getMethod(),
                request.getParameterMap().keySet());
        if (null == method) {
            logger.info("No Service Method FOUND.");
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        }
        //获取方法参数名
        String[] parameterNames = NameDiscover.getParameterNames(method);
///        Object[] parameterNames = request.getParameterMap().keySet().toArray();

        logger.info("[Swagger-dubbo] Invoke by " + cluster);
        //获取服务代理
        Object proxy = null;
        if (CLUSTER_RPC.equals(cluster)) {
            proxy = ReferenceManager.getProxy(interfaceClass);
            if (null == proxy) {
                logger.info("No Ref Proxy Service FOUND.");
                return new ResponseEntity(HttpStatus.NOT_FOUND);
            }
            method = proxy.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (null == method) {
                logger.info("No Proxy Service Method FOUND.");
                return new ResponseEntity(HttpStatus.NOT_FOUND);
            }
        }
        logger.debug("[Swagger-dubbo] Invoke dubbo service method:{},parameter:{}", method, Json.pretty(request.getParameterMap()));
        Object result;
        if (null == parameterNames || parameterNames.length == 0) {
            result = method.invoke(proxy);
        } else {
            Object[] args = new Object[parameterNames.length];
            Type[] parameterTypes = method.getGenericParameterTypes();
            Class<?>[] parameterClazz = method.getParameterTypes();

            for (int i = 0; i < parameterNames.length; i++) {
                Object suggestPrameterValue = suggestPrameterValue(parameterTypes[i], parameterClazz[i], parameterNames[i], request);
                args[i] = suggestPrameterValue;
            }

            result = method.invoke(proxy, args);
        }
        return ResponseEntity.ok(Json.mapper().writeValueAsString(result));
    }


    private Object suggestPrameterValue(Type type, Class<?> cls, String parameterName, HttpServletRequest request) {
        String parameter = request.getParameter(parameterName);
        if (StringUtils.isBlank(parameter)) {
            parameter = getJson(request);
        }
        PrimitiveType fromType = PrimitiveType.fromType(type);
        if (null != fromType) {
            DefaultConversionService service = new DefaultConversionService();
            boolean actual = service.canConvert(String.class, cls);
            if (actual) {
                return service.convert(parameter, cls);
            }
        } else {
            if (null == parameter) {
                return null;
            }
            try {
                return Json.mapper().readValue(parameter, cls);
            } catch (Exception e) {
                throw new IllegalArgumentException("The parameter value [" + parameter + "] should be json of [" + cls.getName() + "] Type.", e);
            }
        }
        try {
            return Class.forName(cls.getName()).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getJson(HttpServletRequest request) {
        try {
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
            StringBuilder responseStrBuilder = new StringBuilder();
            String inputStr = null;
            while (true) {

                if (!((inputStr = streamReader.readLine()) != null)) {
                    break;
                }
                responseStrBuilder.append(inputStr);
            }
            return responseStrBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

}
